/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package wechat

import (
	"gitee.com/tugoer/service"
	"time"
	"gitee.com/tugoer/log"
	"encoding/json"
	"net/http"
	"sync"
)

var container = service.NewContainer(true)

type AccessToken struct {
	Value     string        `json:"access_token"`
	ExpiresIn time.Duration `json:"expires_in"`
	ExpiresAt time.Time
	ErrCode   int    `json:"errcode"`
	ErrMsg    string `json:"errmsg"`
}

type Ticket struct {
	ErrCode   int           `json:"errcode"`
	ErrMsg    string        `json:"errmsg"`
	Value     string        `json:"ticket"`
	ExpiresIn time.Duration `json:"expires_in"`
	ExpiresAt time.Time
}

// provider 根据配置的所有公众号产生
var providers = struct {
	Access map[string]service.Provider
	Tickets map[string]service.Provider
	sync.Mutex
}{
	Access: make(map[string]service.Provider, 5),
	Tickets: make(map[string]service.Provider, 5),
}

func getAccessProvider(originID string) service.Provider {
	if originID == "" {
		originID = EnvContainer.MainOrigin
	}

	providers.Lock()
	defer providers.Unlock()
	return providers.Access[originID]
}

func getTicketProvider(originID string) service.Provider {
	if originID == "" {
		originID = EnvContainer.MainOrigin
	}
	providers.Lock()
	defer providers.Unlock()
	return providers.Tickets[originID]
}

func init() {
	for _, e := range EnvContainer.Namespaces {
		providers.Access[e.OriginID] = newAccessProvider(e)
		providers.Tickets[e.OriginID] = newTicketProvider(e)
	}
}

func newAccessProvider(e *Env) service.Provider {
	return func(c *service.Container) interface{} {
		now := time.Now()
		res, err := http.Get("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + e.AppID + "&secret=" + e.Secret)
		if err != nil {
			log.Emergency.Printf("获取 access_token 出错：%s\n", err)
		} else {
			token := &AccessToken{}
			err = json.NewDecoder(res.Body).Decode(token)
			if err != nil {
				log.Emergency.Printf("解析 access_token 出错：%s\n", err)
			}
			res.Body.Close()
			if token.ErrCode != 0 {
				log.Emergency.Printf("获取 access_token 出错, %d: %s\n", token.ErrCode, token.ErrMsg)
			} else {
				// 提前 10 分钟结束
				token.ExpiresAt = now.Add((token.ExpiresIn - 600) * time.Second)
				return token
			}
		}
		return nil
	}
}

func newTicketProvider(e *Env) service.Provider {
	return func(c *service.Container) interface{} {
		now := time.Now()
		if token, ok := GetAccessToken(e.OriginID); ok {
			res, err := http.Get("https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=" + token.Value)
			if err != nil {
				log.Emergency.Printf("获取 ticket 出错：%s\n", err)
			} else {
				ticket := &Ticket{}
				err = json.NewDecoder(res.Body).Decode(ticket)
				if err != nil {
					log.Emergency.Printf("解析 ticket 出错：%s\n", err)
				}
				res.Body.Close()
				if ticket.ErrCode != 0 {
					log.Emergency.Printf("获取 ticket 出错, %d: %s\n", ticket.ErrCode, ticket.ErrMsg)
				} else {
					ticket.ExpiresAt = now.Add(ticket.ExpiresIn * time.Second)
					return ticket
				}
			}
		}
		return nil
	}
}

// 根据公众号原始 ID 获得 access token, 如果 originID 为空，则默认使用基础公众号原始 ID
func GetAccessToken(originID string) (token *AccessToken, ok bool) {
	accessProvider := getAccessProvider(originID)
	if access, ok := container.Get(&accessProvider).(*AccessToken); ok && access.ExpiresAt.After(time.Now()) {
		return access, true
	} else if access, ok = container.GetNew(&accessProvider).(*AccessToken); ok {
		container.SetCache(&accessProvider, access)
		return access, true
	} else {
		return nil, false
	}
}

// 根据公众号原始 ID 获得 ticket, 如果 originID 为空，则默认使用基础公众号原始 ID
func GetTicket(originID string) (ticket *Ticket, ok bool) {
	ticketProvider := getTicketProvider(originID)
	if ticket, ok := container.Get(&ticketProvider).(*Ticket); ok && ticket.ExpiresAt.Before(time.Now()) {
		return ticket, true
	} else if ticket, ok = container.GetNew(&ticketProvider).(*Ticket); ok {
		container.SetCache(&ticketProvider, ticket)
		return ticket, true
	} else {
		return nil, false
	}
}
